#pragma once

#include<iostream>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<strings.h>
#include<string>
#include<string.h>

#include"Logger.hpp"

const static int gdefaultsockfd = -1;

class UdpServer
{
public:
    UdpServer(uint16_t port)
    :_port(port)
    ,_sockfd(gdefaultsockfd)
    ,_is_running(false)
    {}
    //初始化
    void Init()
    {
        //int socket(int domain, int type, int protocol); 第一个参数为协议簇或者域,表示操作系统创建这个sockfd
        //的用途,相当于是16位地址类型,UDP为网络通信,使用AF_INET就可以了 ,AF :地址族:address family
        //第二个参数表示套接字所对应的类型,是tcp还是udp... 使用udp ,udp 为数据报datagram,即SOCK_DGRAM
        //第三个参数填0
        _sockfd = socket(AF_INET , SOCK_DGRAM ,0);//成功返回文件描述符,失败返回-1
        if(_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "创建socket 失败";
            return;
        }
        LOG(LogLevel::FATAL) << "创建socket 成功";

        //网络通信还需要ip和端口号,从外部传进来,利用命令行参数;并将其与文件捆绑
        //bind :     int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
        //第一个参数为文件描述符,第二个参数为结构体,sockaddr 结构体,网络通信,使用sockaddr_in ,需要强转
        //在sockaddr_in 中填写端口号以及ip地址;第三个参数表示传进来的结构体大小;成功返回0,失败返回-1
        //bind , 首先是填充port 和 ip
        struct sockaddr_in local;
        //将local 清空
        bzero(&local ,sizeof(local));
        //需要考虑主机中的信息向网络中写入的大小端问题, 使用 htonl htons 可以解决
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        //还需要注意的是,喜欢用点分十进制来看ip , 但网路中传递ip 信息的时候,使用的是4字节整数
        //需要将字符串式的点分十进制ip 转换为整数 -> 接口: inet_addr
        //in_addr_t inet_addr(const char *cp);
        //需要注意的是,inet_addr 会自动转换为大端
        // local.sin_addr.s_addr = inet_addr(_ip.c_str());
        //绑定了具体的ip 就只能用这个具体的ip进行通信
        //任意 ip : INADDR_ANY ；一个主机拥有多个IP，相当于为这个IP均进行了绑定；INADDR_ANY 为宏，注意主机转网络
        local.sin_addr.s_addr = htonl(INADDR_ANY);

        int n= bind(_sockfd , (struct sockaddr*)&local , sizeof(local));
        if(n<0)
        {
            LOG(LogLevel::FATAL) << "bind socket 失败";
        }
        LOG(LogLevel::FATAL) << "bind socket 成功" << _sockfd;//文件描述符 3
    }
    //启动服务
    void Start()
    {
       _is_running = true;
       //服务器是一个死循环
        while(_is_running)
        {
            //服务器端读取数据、将收到的数据发送给发送方（客户端）
            //从文件中读取数据 -> recvfrom 
            //ssize_t recvfrom(int sockfd, void buf[restrict .len], size_t len,
            //int flags,struct sockaddr *_Nullable restrict src_addr,
            //socklen_t *_Nullable restrict addrlen);
            //第一个参数为文件描述符，第二个参数为存放读取到的数据的缓冲区，第三个参数为该缓冲区的大小
            //第四个参数为读取策略， 0 表示阻塞读取；第5个参数结构体表示是谁发送过来的，输出型参数，第6个参数为这个结构体的大小
            char buffer[1024];
            buffer[0]=0;
            //输出型参数，谁发过来的数据 
            struct sockaddr_in peer;
            memset(&peer, 0 ,sizeof(peer));
            socklen_t len = sizeof(peer);
            //从文件中读取数据;成功返回读取的个数，失败返回-1
            ssize_t n = recvfrom(_sockfd ,buffer , sizeof(buffer) , 0 , (struct sockaddr*)&peer,&len);
            if(n > 0)
            {
                //手动添加 \0
                buffer[n] =0;
                // LOG(LogLevel::DEBUG) << "客户 say# " << buffer;

                //还可以debug 一下，读取到的数据的客户的ip 与端口号
                std::string clientip = inet_ntoa(peer.sin_addr);
                //同时需要注意网络转主机 
                uint16_t clientport = ntohs(peer.sin_port);
                LOG(LogLevel::DEBUG) << "[" << clientip << ":" << clientport <<"]# " << buffer; 

                //服务器需要向客户端发送同样的信息
                std::string echo_string = "server echo# ";
                echo_string += buffer;
                //使用sendto
                //ssize_t sendto(int sockfd, const void buf[.len], size_t len, int flags,
                //const struct sockaddr *dest_addr, socklen_t addrlen);
                //第一个参数为文件描述符，第二个参数为要发送的数据，第三个参数为数据长度，第四个参数填0
                //第5个参数表示目标主机所对应的套接字信息，也就是peer , 第6个参数为peer 的大小
                sendto(_sockfd , echo_string.c_str() , echo_string.size() , 0, (struct sockaddr*)&peer,sizeof(peer));
            }
        }
        _is_running = false;
    }
    ~UdpServer()
    {}
private:
    int _sockfd;//文件描述符 --> 使用socket 获取
    //端口号以及ip
    uint16_t _port;
    // std::string _ip;//使用点分十进制的ip 可读性更好

    bool _is_running;
};